<!DOCTYPE HTML>
<!--
	Dimension by HTML5 UP
	html5up.net | @ajlkn
	Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
 <head>
  <title>
   Dimension by HTML5 UP
  </title>
  <!-- <meta charset="utf-8" /> -->
  <!-- <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> -->
  <meta charset="utf-8"/>
  <meta content="width=device-width,initial-scale=1.0" name="viewport"/>
  <link href="../../assets/css/article.css" rel="stylesheet"/>
  <link href="https://cdn.bootcss.com/highlight.js/9.15.8/styles/github.min.css" rel="stylesheet"/>
  <noscript>
   <link href="../../assets/css/noscript.css" rel="stylesheet"/>
  </noscript>
 </head>
 <body>
  <div id="app">
  </div>
  <!-- built files will be auto injected -->
 </body>
 <body class="is-preload">
  <!-- Wrapper -->
  <div id="wrapper">
   <!-- Main -->
   <div id="main">
    <article id="article">
     <h1 id="soulzookeeper-admin">
      Soul网关源码解析（十八）Zookeeper数据同步初探-Admin端
     </h1>
     <hr/>
     <h2 id="_1">
      简介
     </h2>
     <p>
      本篇文章来初步探索下Soul-Admin端Zookeeper如何初始化数据、处理更新数据的流程
     </p>
     <h2 id="_2">
      概览
     </h2>
     <p>
      从朱明老哥的：
      <a href="https://blog.csdn.net/zm469568595/article/details/113065463">
       Soul网关源码分析-11期
      </a>
      文章中定位到Soul-Admin中Zookeeper数据同步相关的配置类位置，而后进行探索分析
     </p>
     <p>
      了解到在没有数据的情况下，会从数据库中读取所有的数据，更新到Zookeeper中；而有数据的情况下，不会进行更新
     </p>
     <p>
      通过在插件更新的处理函数中，通过调用栈，初步梳理得到Zookeeper同步更新数据的大致流程
     </p>
     <ul>
      <li>
       1.后台管理界面（或者注册Client，猜测）发起请求到HTTP接口，触发数据更新
      </li>
      <li>
       2.Admin后台接口触发，调用相应Service进行处理
      </li>
      <li>
       3.事件发布中心进行事件发布
      </li>
      <li>
       4.Zookeeper数据同步组件收到相关事件，进行处理
      </li>
     </ul>
     <p>
      具体解析日志请看源码Debug
     </p>
     <h2 id="debug">
      源码Debug
     </h2>
     <h3 id="_3">
      寻找切入点
     </h3>
     <p>
      首先根据朱明老哥的：
      <a href="https://blog.csdn.net/zm469568595/article/details/113065463">
       Soul网关源码分析-11期
      </a>
      文章定位到Admin模块中的数据同步的核心配置类，代码如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DataSyncConfiguration</span> <span class="p">{</span>

    <span class="nd">@Configuration</span>
    <span class="nd">@ConditionalOnProperty</span><span class="p">(</span><span class="n">prefix</span> <span class="o">=</span> <span class="s">"soul.sync.zookeeper"</span><span class="p">,</span> <span class="n">name</span> <span class="o">=</span> <span class="s">"url"</span><span class="p">)</span>
    <span class="nd">@Import</span><span class="p">(</span><span class="n">ZookeeperConfiguration</span><span class="p">.</span><span class="na">class</span><span class="p">)</span>
    <span class="kd">static</span> <span class="kd">class</span> <span class="nc">ZookeeperListener</span> <span class="p">{</span>

        <span class="cm">/**</span>
<span class="cm">         * Config event listener data changed listener.</span>
<span class="cm">         */</span>
        <span class="nd">@Bean</span>
        <span class="nd">@ConditionalOnMissingBean</span><span class="p">(</span><span class="n">ZookeeperDataChangedListener</span><span class="p">.</span><span class="na">class</span><span class="p">)</span>
        <span class="kd">public</span> <span class="n">DataChangedListener</span> <span class="nf">zookeeperDataChangedListener</span><span class="p">(</span><span class="kd">final</span> <span class="n">ZkClient</span> <span class="n">zkClient</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="n">ZookeeperDataChangedListener</span><span class="p">(</span><span class="n">zkClient</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="cm">/**</span>
<span class="cm">         * Zookeeper data init zookeeper data init.</span>
<span class="cm">         */</span>
        <span class="nd">@Bean</span>
        <span class="nd">@ConditionalOnMissingBean</span><span class="p">(</span><span class="n">ZookeeperDataInit</span><span class="p">.</span><span class="na">class</span><span class="p">)</span>
        <span class="kd">public</span> <span class="n">ZookeeperDataInit</span> <span class="nf">zookeeperDataInit</span><span class="p">(</span><span class="kd">final</span> <span class="n">ZkClient</span> <span class="n">zkClient</span><span class="p">,</span> <span class="kd">final</span> <span class="n">SyncDataService</span> <span class="n">syncDataService</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="n">ZookeeperDataInit</span><span class="p">(</span><span class="n">zkClient</span><span class="p">,</span> <span class="n">syncDataService</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      在上面我们看到Websocket、HTTP、Zookeeper、Nacos数据同步的相关配置（这里只列出Zookeeper），我们可以看到一个是监听的，一个是初始化的
     </p>
     <p>
      这里有个小插曲，在Websocket的配置注解如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DataSyncConfiguration</span> <span class="p">{</span>

    <span class="nd">@Configuration</span>
    <span class="nd">@ConditionalOnProperty</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"soul.sync.websocket.enabled"</span><span class="p">,</span> <span class="n">havingValue</span> <span class="o">=</span> <span class="s">"true"</span><span class="p">,</span> <span class="n">matchIfMissing</span> <span class="o">=</span> <span class="kc">true</span><span class="p">)</span>
    <span class="nd">@EnableConfigurationProperties</span><span class="p">(</span><span class="n">WebsocketSyncProperties</span><span class="p">.</span><span class="na">class</span><span class="p">)</span>
    <span class="kd">static</span> <span class="kd">class</span> <span class="nc">WebsocketListener</span> <span class="p">{</span>
        <span class="p">.......</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      在前面的文章中，我们遇到一个将Websocket同步配置给注释掉，但发现Admin还是开启了Websocket的同步，其中的原因就在这，从上面的注解中可以看到，如果没有是否开启的值，那默认就为true（表示开启）
     </p>
     <p>
      那Admin关闭Websocket同步就只有两种方式：
     </p>
     <ul>
      <li>
       1.将配置enabled设置为false
      </li>
      <li>
       2.将Websocket starter依赖去掉（Admin模块如果有的话）
      </li>
     </ul>
     <h3 id="_4">
      数据初始化
     </h3>
     <p>
      下面我们来探索下Zookeeper数据的初始化，下面的类可以看出是一个初始化的类，并且是启动时运行的
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ZookeeperDataInit</span> <span class="kd">implements</span> <span class="n">CommandLineRunner</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span><span class="p">...</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">String</span> <span class="n">pluginPath</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">PLUGIN_PARENT</span><span class="p">;</span>
        <span class="n">String</span> <span class="n">authPath</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">APP_AUTH_PARENT</span><span class="p">;</span>
        <span class="n">String</span> <span class="n">metaDataPath</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">META_DATA</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">zkClient</span><span class="p">.</span><span class="na">exists</span><span class="p">(</span><span class="n">pluginPath</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">zkClient</span><span class="p">.</span><span class="na">exists</span><span class="p">(</span><span class="n">authPath</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">zkClient</span><span class="p">.</span><span class="na">exists</span><span class="p">(</span><span class="n">metaDataPath</span><span class="p">))</span> <span class="p">{</span>
            <span class="c1">// 从名称可以看出是同步所有的数据</span>
            <span class="n">syncDataService</span><span class="p">.</span><span class="na">syncAll</span><span class="p">(</span><span class="n">DataEventTypeEnum</span><span class="p">.</span><span class="na">REFRESH</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们在run函数上打上断点，果然在重启Admin后进入该断点，但发现在Debug的时候没有能进入syncAll函数
     </p>
     <p>
      猜测是Zookeeper为空才进行初始化
     </p>
     <p>
      我们将Zookeeper中的soul节点删除，清空所有的数据
     </p>
     <p>
      重启Admin进入断点，并进入run函数，发现成功进入syncAll函数，我们来看看其代码：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="nd">@Service</span><span class="p">(</span><span class="s">"syncDataService"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SyncDataServiceImpl</span> <span class="kd">implements</span> <span class="n">SyncDataService</span> <span class="p">{</span>

    <span class="c1">// 基本逻辑就是从数据库中读取数据，然后发布事件</span>
    <span class="c1">// 这里读取了目前的五种数据并进行事件发布</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">syncAll</span><span class="p">(</span><span class="kd">final</span> <span class="n">DataEventTypeEnum</span> <span class="n">type</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// type : refresh</span>
        <span class="c1">// 里面也是读取数据数据，然后发布事件</span>
        <span class="n">appAuthService</span><span class="p">.</span><span class="na">syncData</span><span class="p">();</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="n">pluginDataList</span> <span class="o">=</span> <span class="n">pluginService</span><span class="p">.</span><span class="na">listAll</span><span class="p">();</span>
        <span class="n">eventPublisher</span><span class="p">.</span><span class="na">publishEvent</span><span class="p">(</span><span class="k">new</span> <span class="n">DataChangedEvent</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">PLUGIN</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span> <span class="n">pluginDataList</span><span class="p">));</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">SelectorData</span><span class="o">&gt;</span> <span class="n">selectorDataList</span> <span class="o">=</span> <span class="n">selectorService</span><span class="p">.</span><span class="na">listAll</span><span class="p">();</span>
        <span class="n">eventPublisher</span><span class="p">.</span><span class="na">publishEvent</span><span class="p">(</span><span class="k">new</span> <span class="n">DataChangedEvent</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">SELECTOR</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span> <span class="n">selectorDataList</span><span class="p">));</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">RuleData</span><span class="o">&gt;</span> <span class="n">ruleDataList</span> <span class="o">=</span> <span class="n">ruleService</span><span class="p">.</span><span class="na">listAll</span><span class="p">();</span>
        <span class="n">eventPublisher</span><span class="p">.</span><span class="na">publishEvent</span><span class="p">(</span><span class="k">new</span> <span class="n">DataChangedEvent</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">RULE</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span> <span class="n">ruleDataList</span><span class="p">));</span>
        <span class="n">metaDataService</span><span class="p">.</span><span class="na">syncData</span><span class="p">();</span>
        <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      在上面的函数中，pluginService是我们熟悉不过的CRUD相关的东西了，这里就不多说了
     </p>
     <p>
      可以明显的看出，初始化就是从数据库中读取所有的数据，然后发布数据更新事件，细节这里先不看，我们下面来看看一个数据更新的处理流程
     </p>
     <h3 id="_5">
      插件数据更新处理流程
     </h3>
     <p>
      我们看看另外一个数据监听的类：ZookeeperDataChangedListener,在其中找到了非常可疑的插件更新的处理函数，我们在其上打上断点
     </p>
     <p>
      然后我们在Admin后台管理界面中将限流插件的状态进行修改，果然进入了断点之中，稍微跟踪了下，最终的数据会写入Zookeeper节点中。这样Zookeeper数据发生变化，就会推送数据给Bootstrap，Bootstrap的数据也就跟着更新了
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ZookeeperDataChangedListener</span> <span class="kd">implements</span> <span class="n">DataChangedListener</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onPluginChanged</span><span class="p">(</span><span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="n">changed</span><span class="p">,</span> <span class="kd">final</span> <span class="n">DataEventTypeEnum</span> <span class="n">eventType</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">PluginData</span> <span class="n">data</span> <span class="p">:</span> <span class="n">changed</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">final</span> <span class="n">String</span> <span class="n">pluginPath</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">buildPluginPath</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span>
            <span class="c1">// delete</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">eventType</span> <span class="o">==</span> <span class="n">DataEventTypeEnum</span><span class="p">.</span><span class="na">DELETE</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">deleteZkPathRecursive</span><span class="p">(</span><span class="n">pluginPath</span><span class="p">);</span>
                <span class="kd">final</span> <span class="n">String</span> <span class="n">selectorParentPath</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">buildSelectorParentPath</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span>
                <span class="n">deleteZkPathRecursive</span><span class="p">(</span><span class="n">selectorParentPath</span><span class="p">);</span>
                <span class="kd">final</span> <span class="n">String</span> <span class="n">ruleParentPath</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">buildRuleParentPath</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span>
                <span class="n">deleteZkPathRecursive</span><span class="p">(</span><span class="n">ruleParentPath</span><span class="p">);</span>
                <span class="k">continue</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="c1">//create or update</span>
            <span class="n">upsertZkNode</span><span class="p">(</span><span class="n">pluginPath</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们接着看调用栈，看看其处理流程
     </p>
     <p>
      跟踪调用栈，来到下面的函数，可以看到和Bootstrap的挺像的，下面接收事件后，根据其GoupKey，进行分发处理，getSource应该就是具体的插件数据，另外一个是事件类型，是更新还是删除
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DataChangedEventDispatcher</span> <span class="kd">implements</span> <span class="n">ApplicationListener</span><span class="o">&lt;</span><span class="n">DataChangedEvent</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">InitializingBean</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="nd">@SuppressWarnings</span><span class="p">(</span><span class="s">"unchecked"</span><span class="p">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onApplicationEvent</span><span class="p">(</span><span class="kd">final</span> <span class="n">DataChangedEvent</span> <span class="n">event</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">DataChangedListener</span> <span class="n">listener</span> <span class="p">:</span> <span class="n">listeners</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">switch</span> <span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="na">getGroupKey</span><span class="p">())</span> <span class="p">{</span>
                <span class="k">case</span> <span class="n">APP_AUTH</span><span class="p">:</span>
                    <span class="n">listener</span><span class="p">.</span><span class="na">onAppAuthChanged</span><span class="p">((</span><span class="n">List</span><span class="o">&lt;</span><span class="n">AppAuthData</span><span class="o">&gt;</span><span class="p">)</span> <span class="n">event</span><span class="p">.</span><span class="na">getSource</span><span class="p">(),</span> <span class="n">event</span><span class="p">.</span><span class="na">getEventType</span><span class="p">());</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">PLUGIN</span><span class="p">:</span>
                    <span class="n">listener</span><span class="p">.</span><span class="na">onPluginChanged</span><span class="p">((</span><span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span><span class="p">)</span> <span class="n">event</span><span class="p">.</span><span class="na">getSource</span><span class="p">(),</span> <span class="n">event</span><span class="p">.</span><span class="na">getEventType</span><span class="p">());</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RULE</span><span class="p">:</span>
                    <span class="n">listener</span><span class="p">.</span><span class="na">onRuleChanged</span><span class="p">((</span><span class="n">List</span><span class="o">&lt;</span><span class="n">RuleData</span><span class="o">&gt;</span><span class="p">)</span> <span class="n">event</span><span class="p">.</span><span class="na">getSource</span><span class="p">(),</span> <span class="n">event</span><span class="p">.</span><span class="na">getEventType</span><span class="p">());</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">SELECTOR</span><span class="p">:</span>
                    <span class="n">listener</span><span class="p">.</span><span class="na">onSelectorChanged</span><span class="p">((</span><span class="n">List</span><span class="o">&lt;</span><span class="n">SelectorData</span><span class="o">&gt;</span><span class="p">)</span> <span class="n">event</span><span class="p">.</span><span class="na">getSource</span><span class="p">(),</span> <span class="n">event</span><span class="p">.</span><span class="na">getEventType</span><span class="p">());</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">META_DATA</span><span class="p">:</span>
                    <span class="n">listener</span><span class="p">.</span><span class="na">onMetaDataChanged</span><span class="p">((</span><span class="n">List</span><span class="o">&lt;</span><span class="n">MetaData</span><span class="o">&gt;</span><span class="p">)</span> <span class="n">event</span><span class="p">.</span><span class="na">getSource</span><span class="p">(),</span> <span class="n">event</span><span class="p">.</span><span class="na">getEventType</span><span class="p">());</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">default</span><span class="p">:</span>
                    <span class="k">throw</span> <span class="k">new</span> <span class="n">IllegalStateException</span><span class="p">(</span><span class="s">"Unexpected value: "</span> <span class="o">+</span> <span class="n">event</span><span class="p">.</span><span class="na">getGroupKey</span><span class="p">());</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们继续跟踪调用栈来到下面的函数，发现是一个CRUD的Service，其中的处理逻辑是：先进行数据库更新，然后发布事件
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="nd">@Service</span><span class="p">(</span><span class="s">"pluginService"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PluginServiceImpl</span> <span class="kd">implements</span> <span class="n">PluginService</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="nd">@Transactional</span><span class="p">(</span><span class="n">rollbackFor</span> <span class="o">=</span> <span class="n">Exception</span><span class="p">.</span><span class="na">class</span><span class="p">)</span>
    <span class="kd">public</span> <span class="n">String</span> <span class="nf">createOrUpdate</span><span class="p">(</span><span class="kd">final</span> <span class="n">PluginDTO</span> <span class="n">pluginDTO</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">final</span> <span class="n">String</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">checkData</span><span class="p">(</span><span class="n">pluginDTO</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">StringUtils</span><span class="p">.</span><span class="na">isNoneBlank</span><span class="p">(</span><span class="n">msg</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">msg</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">PluginDO</span> <span class="n">pluginDO</span> <span class="o">=</span> <span class="n">PluginDO</span><span class="p">.</span><span class="na">buildPluginDO</span><span class="p">(</span><span class="n">pluginDTO</span><span class="p">);</span>
        <span class="n">DataEventTypeEnum</span> <span class="n">eventType</span> <span class="o">=</span> <span class="n">DataEventTypeEnum</span><span class="p">.</span><span class="na">CREATE</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">StringUtils</span><span class="p">.</span><span class="na">isBlank</span><span class="p">(</span><span class="n">pluginDTO</span><span class="p">.</span><span class="na">getId</span><span class="p">()))</span> <span class="p">{</span>
            <span class="n">pluginMapper</span><span class="p">.</span><span class="na">insertSelective</span><span class="p">(</span><span class="n">pluginDO</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">eventType</span> <span class="o">=</span> <span class="n">DataEventTypeEnum</span><span class="p">.</span><span class="na">UPDATE</span><span class="p">;</span>
            <span class="n">pluginMapper</span><span class="p">.</span><span class="na">updateSelective</span><span class="p">(</span><span class="n">pluginDO</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// publish change event.</span>
        <span class="n">eventPublisher</span><span class="p">.</span><span class="na">publishEvent</span><span class="p">(</span><span class="k">new</span> <span class="n">DataChangedEvent</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">PLUGIN</span><span class="p">,</span> <span class="n">eventType</span><span class="p">,</span>
                <span class="n">Collections</span><span class="p">.</span><span class="na">singletonList</span><span class="p">(</span><span class="n">PluginTransfer</span><span class="p">.</span><span class="na">INSTANCE</span><span class="p">.</span><span class="na">mapToData</span><span class="p">(</span><span class="n">pluginDO</span><span class="p">))));</span>
        <span class="k">return</span> <span class="n">StringUtils</span><span class="p">.</span><span class="na">EMPTY</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      继续跟，我们来到Controller,其逻辑也很简单，调用相应的Service即可，这里也不过多赘述了。到这里大致的流程就走了一遍
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="p">(</span><span class="s">"/plugin"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PluginController</span> <span class="p">{</span>
    <span class="nd">@PutMapping</span><span class="p">(</span><span class="s">"/{id}"</span><span class="p">)</span>
    <span class="kd">public</span> <span class="n">SoulAdminResult</span> <span class="nf">updatePlugin</span><span class="p">(</span><span class="nd">@PathVariable</span><span class="p">(</span><span class="s">"id"</span><span class="p">)</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">id</span><span class="p">,</span> <span class="nd">@RequestBody</span> <span class="kd">final</span> <span class="n">PluginDTO</span> <span class="n">pluginDTO</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNull</span><span class="p">(</span><span class="n">pluginDTO</span><span class="p">);</span>
        <span class="n">pluginDTO</span><span class="p">.</span><span class="na">setId</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
        <span class="kd">final</span> <span class="n">String</span> <span class="n">result</span> <span class="o">=</span> <span class="n">pluginService</span><span class="p">.</span><span class="na">createOrUpdate</span><span class="p">(</span><span class="n">pluginDTO</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">StringUtils</span><span class="p">.</span><span class="na">isNoneBlank</span><span class="p">(</span><span class="n">result</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">SoulAdminResult</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">SoulAdminResult</span><span class="p">.</span><span class="na">success</span><span class="p">(</span><span class="n">SoulResultMessage</span><span class="p">.</span><span class="na">UPDATE_SUCCESS</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <h2 id="_6">
      总结
     </h2>
     <p>
      本篇文章进行了初步探索的Admin端的Zookeeper数据同步的处理流程，大致可以分为初始化和数据更新（包括删除）的处理流程
     </p>
     <ul>
      <li>
       数据初始化：ZookeeperDataInit
      </li>
      <li>
       Zookeeper中不为空，则不进行初始化操作
      </li>
      <li>
       <p>
        Zookeeper为空，则进行初始化操作，从数据库中读取数据，放入到其中
       </p>
      </li>
      <li>
       <p>
        数据处理流程(监听类ZookeeperDataChangedListener)
       </p>
      </li>
      <li>
       HTTP接口调用：可以是管理后台；也可以是服务注册Client
      </li>
      <li>
       Service调用：更新数据库中的数据，调用发布事件接口
      </li>
      <li>
       发布事件：发布事件到数据同步监听中
      </li>
      <li>
       数据更新：接收到事件后，进行更新（Websocket进行推送、Zookeeper写入、HTTP更新MD5、Nacos写入）
      </li>
     </ul>
     <p>
      根据此次的分析，大致猜测如上
     </p>
     <p>
      此次还意外解决之前的Websocket数据同步关闭失效问题
     </p>
    </article>
   </div>
   <!-- Footer -->
   <footer id="footer">
    <p class="copyright">
     © Untitled. Design:
     <a href="https://html5up.net">
      HTML5 UP
     </a>
     .
    </p>
   </footer>
  </div>
  <!-- BG -->
  <div id="bg">
  </div>
  <!-- Scripts -->
  <script src="../assets/js/jquery.min.js">
  </script>
  <script src="../assets/js/browser.min.js">
  </script>
  <script src="../assets/js/breakpoints.min.js">
  </script>
  <script src="../assets/js/util.js">
  </script>
  <script src="../assets/js/main.js">
  </script>
 </body>
</html>
